#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using Castle.ActiveRecord;
using log4net;
using Karma.Framework.Core.Domain.Tools.Import.Layout;
using Karma.Framework.Core.Domain.Tools.Import.Report;
using Karma.Framework.Core.Utils;

#endregion

namespace Karma.Framework.Core.Domain.Tools.Import.Data
{
    public class DefaultEntityPersister : EntityPersisterBase
    {
        private static readonly ILog LOG = LogManager.GetLogger(typeof (DefaultEntityPersister).Name);

        public override OperationsSummary Persist<T>(EntityLayout layout, IList<T> entities) 
        {
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug("Starting persistence process...");
            }
            OnBeforePersistAll(new BeforePersistAllEntitiesEventArgs(typeof (T), null));
            var toSave = (from e in entities
                          where e.Id == 0
                          select e).ToList();

            var toUpdate = (from e in entities
                            where e.Id > 0
                            select e).ToList();

            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Entities to insert: [{0}]", toSave.ItemsToString()));
                LOG.Debug(string.Format("Entities to update: [{0}]", toUpdate.ItemsToString()));
            }
            var inserted = 0;
            var updated = 0;
            var session = ActiveRecordMediator.GetSessionFactoryHolder ().CreateSession (typeof (T));
            using (var tx = session.BeginTransaction())
            {
                session.SetBatchSize(1000);
                foreach (var entity in toSave)
                {
                    try
                    {
                        OnBeforePersistEntity(new BeforePersistEntityEventArgs(typeof(T), entity));
                        ActiveRecordMediator.Save(entity);
                        OnAfterPersistEntity(new AfterPersistEntityEventArgs(typeof(T), entity));
                        inserted++;
                    }
                    catch (Exception ex)
                    {
                        if (LOG.IsErrorEnabled)
                        {
                            LOG.Error(string.Format("Entity failed to update: [{0}]", entity), ex);
                        }
                    }
                }
                foreach (var entity in toUpdate)
                {
                    try
                    {
                        var origEntity = (T)ActiveRecordMediator.FindByPrimaryKey(typeof(T), entity.Id);
                        var updEntity = UpdateEntityProperties(layout, origEntity, entity);
                        OnBeforeUpdateEntity(new BeforeUpdateEntityEventArgs(typeof(T), entity));
                        ActiveRecordMediator.Update(updEntity);
                        OnAfterUpdateEntity(new AfterUpdateEntityEventArgs(typeof(T), updEntity));
                        updated++;
                    }
                    catch (Exception ex)
                    {
                        if (LOG.IsErrorEnabled)
                        {
                            LOG.Error(string.Format("Entity failed to update: [{0}]", entity), ex);
                        }
                    }
                }
                tx.Commit();
            }
            ActiveRecordMediator.GetSessionFactoryHolder().ReleaseSession(session);
            OnAfterPersistAll(new AfterPersistAllEntitiesEventArgs(typeof (T), entities.Cast<DomainObject>().ToList()));
            if (LOG.IsDebugEnabled)
            {
                LOG.Debug(string.Format("Persistence process finished...Saved: [{0}], Updated: [{1}]", inserted, updated));
            }
            return new OperationsSummary
                       {
                           Inserted = inserted,
                           Updated = updated
                       };
        }

        private T UpdateEntityProperties<T>(EntityLayout layout, T origEntity, T newEntity)
        {
            var type = typeof (T);
            foreach (var property in layout.Properties)
            {
                // compare property values for each property
                var pinfo = type.GetProperty(property.Name, property.FieldType);
                var origValue = pinfo.GetValue(origEntity, new object[] {});
                var newValue = pinfo.GetValue(newEntity, new object[] {});
                if (property is ReferenceField)
                {
                    if (origValue == null)
                    {
                        pinfo.SetValue(origEntity, newValue, new object[] {});
                    }
                    else
                    {
                        var idinfo = origValue.GetType().GetProperty("Id");
                        var origId = idinfo.GetValue(origValue, new object[] {});
                        var newId = idinfo.GetValue(newValue, new object[] {});

                        if (newValue != null && !origId.Equals(newId))
                        {
                            pinfo.SetValue(origEntity, newValue, new object[] {});
                        }
                    }
                }
                else
                {
                    if (newValue != null && !origValue.Equals(newValue))
                    {
                        pinfo.SetValue(origEntity, newValue, new object[] {});
                    }
                }
            }
            return origEntity;
        }
    }
}